package com.atguigu.tingshu.account.service.impl;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.account.mapper.UserAccountDetailMapper;
import com.atguigu.tingshu.account.mapper.UserAccountMapper;
import com.atguigu.tingshu.account.service.UserAccountService;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.model.account.UserAccount;
import com.atguigu.tingshu.model.account.UserAccountDetail;
import com.atguigu.tingshu.vo.account.AccountLockResultVo;
import com.atguigu.tingshu.vo.account.AccountLockVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserAccount> implements UserAccountService {

    private final UserAccountMapper userAccountMapper;
    private final RedisTemplate<String, String> redisTemplate;
    private final UserAccountDetailMapper userAccountDetailMapper;

    @Autowired
    public UserAccountServiceImpl(UserAccountMapper userAccountMapper, RedisTemplate<String, String> redisTemplate, UserAccountDetailMapper userAccountDetailMapper) {
        this.userAccountMapper = userAccountMapper;
        this.redisTemplate = redisTemplate;
        this.userAccountDetailMapper = userAccountDetailMapper;
    }

    /**
     * @param userId 新增的用户id
     * @description: 新增账户信息
     * @author Petrichor
     * @date 2023/12/23 20:44
     **/
    @Override
    public void addAccount(String userId) {
        UserAccount userAccount = new UserAccount();
        userAccount.setUserId(Long.parseLong(userId));
        this.save(userAccount);
    }

    @Override
    public BigDecimal getAvailableAmount() {
        // 获取当前登录用户的id
        Long userId = AuthContextHolder.getUserId();
        // 构建查询条件对象
        LambdaQueryWrapper<UserAccount> userAccountLambdaQueryWrapper = new LambdaQueryWrapper<>();
        userAccountLambdaQueryWrapper.eq(UserAccount::getUserId, userId);

        // 进行查询
        UserAccount userAccount = userAccountMapper.selectOne(userAccountLambdaQueryWrapper);

        // 返回剩余金额
        return userAccount.getAvailableAmount();
    }

    /**
     * @param accountLockVo 锁定金额对象
     * @return AccountLockResultVo 锁定金额返回对象
     * @description: 检查并锁定可用余额
     * @author Petrichor
     * @date 2024/1/8 19:47
     **/
    @Override
    public AccountLockResultVo checkAndLock(AccountLockVo accountLockVo) {
        // 定义redis的幂等性保证的key
        String idmpotenKey = "check:idmpotent:" + accountLockVo.getOrderNo();
        // 定义响应结果的数据的rediskey
        String accountLockResultKey = "account:lock:result:" + accountLockVo.getOrderNo();

        // 存储数据到redis中 redis-setnx 存入失败 则证明是重复请求返回false
        Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(idmpotenKey, accountLockVo.getOrderNo(), 1, TimeUnit.HOURS);
        if (!ifAbsent) {
            // 如果是false表示该接口是重复请求
            String dataJSON = redisTemplate.opsForValue().get(accountLockResultKey);
            if (!StrUtil.isEmpty(dataJSON)) {
                // redis中有数据
                AccountLockResultVo accountLockResultVo = JSON.parseObject(dataJSON, AccountLockResultVo.class);
                return accountLockResultVo;
            } else {
                // redis中没有数据
                throw new GuiguException(ResultCodeEnum.ACCOUNT_LOCK_REPEAT);
            }
        }
        // 说明是第一次请求
        //	核对账户可用金额并锁定账户数据(悲观锁)；查询返回的是满足要求的账户
        UserAccount userAccount = userAccountMapper.check(accountLockVo.getUserId(), accountLockVo.getAmount());
        if (null == userAccount) {
            // 删除幂等性key的保证
            redisTemplate.delete(accountLockResultKey);
            throw new GuiguException(ResultCodeEnum.ACCOUNT_LESS);
        }
        // 说明该用户金额足够  锁定用户金额
        int lock = userAccountMapper.lock(accountLockVo.getUserId(), accountLockVo.getAmount());
        if (lock == 0) {
            // 锁定失败
            // 删除幂等性key的保证
            redisTemplate.delete(accountLockResultKey);
            throw new GuiguException(ResultCodeEnum.ACCOUNT_LOCK_ERROR);
        }
        //  锁定成功 添加账户明细
        log(accountLockVo.getUserId(), "锁定:" + accountLockVo.getContent(),
                SystemConstant.ACCOUNT_TRADE_TYPE_LOCK, accountLockVo.getAmount(), accountLockVo.getOrderNo());

        // 构建响应结构数据
        AccountLockResultVo accountLockResultVo = new AccountLockResultVo();
        accountLockResultVo.setUserId(accountLockVo.getUserId());
        accountLockResultVo.setAmount(accountLockVo.getAmount());
        accountLockResultVo.setContent(accountLockVo.getContent());

        // 把响应结果数据存到Redis中
        redisTemplate.opsForValue().set(accountLockResultKey , JSON.toJSONString(accountLockResultVo) , 1 , TimeUnit.HOURS);

        // 返回
        return accountLockResultVo;

    }
    /**
     * @description: 扣减锁定金额 记录账户变更日志
     * @author Petrichor
     * @date 2024/1/9 19:07
     * @param orderNo 订单号
     * @return NULL
     **/
    @Override
    public void minus(String orderNo) {
        // 1>>> 扣减锁定金额
        // 定义保证幂等性的key
        String idempotentKey = "account:minus:idempotent:" + orderNo ;

        // 数据设置失败，说明是重复提交
        Boolean isExist = redisTemplate.opsForValue().setIfAbsent(idempotentKey, orderNo, 1, TimeUnit.HOURS);
        if(!isExist) {
            return ;
        }
        // 锁定账号结果信息key
        String accountLockResultKey = "account:lock:result:" + orderNo ;
        // 在锁定时 会将锁定金额返回对象 放入redis
        String accountLockResultJSON = this.redisTemplate.opsForValue().get(accountLockResultKey) ;
        if(StrUtil.isEmpty(accountLockResultJSON)) {
            return ;
        }
        // 获取 锁定金额返回对象 accountLockResultVo
        AccountLockResultVo accountLockResultVo = JSON.parseObject(accountLockResultJSON, AccountLockResultVo.class);
        // 进行扣减锁定金额
        int minus = userAccountMapper.minus(accountLockResultVo.getUserId(), accountLockResultVo.getAmount());
        if (minus == 0){
            // 说明修改失败 // 删除幂等性保证的key 并报错
            redisTemplate.delete(idempotentKey) ;
            throw new GuiguException(ResultCodeEnum.ACCOUNT_MINUSLOCK_ERROR);
        }
        // 2>>> 记录账户变更日志
        //记录日志
        this.log(accountLockResultVo.getUserId(),
                ""+accountLockResultVo.getContent(),
                SystemConstant.ACCOUNT_TRADE_TYPE_MINUS,
                accountLockResultVo.getAmount() ,
                orderNo
        );

        // 解锁账户金额之后，删除锁定缓存
        this.redisTemplate.delete(accountLockResultKey);

    }
    /**
     * @description: 解除锁定金额 记录账户变更日志
     * @author Petrichor
     * @date 2024/1/9 19:07
     * @param orderNo 订单号
     * @return NULL
     **/
    @Override
    public void unlock(String orderNo) {
        // 1>>> 解除锁定金额
        // 定义保证幂等性的key
        String idempotentKey = "account:minus:idempotent:" + orderNo ;

        // 数据设置失败，说明是重复提交
        Boolean isExist = redisTemplate.opsForValue().setIfAbsent(idempotentKey, orderNo, 1, TimeUnit.HOURS);
        if(!isExist) {
            return ;
        }
        // 锁定账号结果信息key
        String accountLockResultKey = "account:lock:result:" + orderNo ;
        // 在锁定时 会将锁定金额返回对象 放入redis
        String accountLockResultJSON = this.redisTemplate.opsForValue().get(accountLockResultKey) ;
        if(StrUtil.isEmpty(accountLockResultJSON)) {
            return ;
        }
        // 获取 锁定金额返回对象 accountLockResultVo
        AccountLockResultVo accountLockResultVo = JSON.parseObject(accountLockResultJSON, AccountLockResultVo.class);
        // 解除锁定金额
        int unlock = userAccountMapper.unlock(accountLockResultVo.getUserId(), accountLockResultVo.getAmount());
        if (unlock == 0){
            // 说明修改失败 // 删除幂等性保证的key 并报错
            redisTemplate.delete(idempotentKey) ;
            throw new GuiguException(ResultCodeEnum.ACCOUNT_MINUSLOCK_ERROR);
        }
        // 2>>> 记录账户变更日志
        //记录日志
        this.log(accountLockResultVo.getUserId(),
                "解锁："+accountLockResultVo.getContent(),
                SystemConstant.ACCOUNT_TRADE_TYPE_MINUS,
                accountLockResultVo.getAmount() ,
                orderNo
        );

        // 解锁账户金额之后，删除锁定缓存
        this.redisTemplate.delete(accountLockResultKey);
    }
    /**
     * @description: 更新余额
     * @author Petrichor
     * @date 2024/1/11 11:31
     * @param userId 用户id
     * @param amount 充值金额
     * @param orderNo 订单号
     * @param tradeType 账号交易类型-充值
     * @param title 交易标题=充值
     **/
    @Override
    public void add(Long userId, BigDecimal amount, String orderNo, String tradeType, String title) {
        // 查看是否有当前对象
        long count = userAccountDetailMapper.selectCount(new LambdaQueryWrapper<UserAccountDetail>().eq(UserAccountDetail::getOrderNo, orderNo));
        if(count > 0) {
            return;
        }

        //添加账号金额
        userAccountMapper.add(userId, amount);

        //添加账户明细
        this.log(userId, title, tradeType, amount, orderNo);
    }
    /**
     * @description:  获取登录用户的记录 根据用户id 交易类型
     * @author Petrichor
     * @date 2024/1/11 11:58
     * @param page 当前页码
     * @param limit 每页记录数
     * @return
     **/
    @Override
    public IPage<UserAccountDetail> findUserTradeTypePage(Long page, Long limit,Long tradeType) {
        Page<UserAccountDetail> pageParam = new Page<>(page,limit);
        IPage<UserAccountDetail> iPage = userAccountDetailMapper.selectUserTradeTypePage(pageParam,AuthContextHolder.getUserId(),tradeType);
        return iPage;
    }

    /**
     * @description: 添加账户明细
     * @author Petrichor
     * @date 2024/1/11 11:35
     * @param userId 用户id
     * @param title 交易标题
     * @param tradeType 账号交易类型
     * @param amount 充值金额
     * @param orderNo 订单号
     * @return
    **/
    private void log(Long userId, String title, String tradeType, BigDecimal amount, String orderNo) {

        // 构建要保存的数据
        UserAccountDetail userAccountDetail = new UserAccountDetail();
        userAccountDetail.setUserId(userId);
        userAccountDetail.setTitle(title);
        userAccountDetail.setTradeType(tradeType);
        userAccountDetail.setAmount(amount);
        userAccountDetail.setOrderNo(orderNo);

        // 保存数据
        userAccountDetailMapper.insert(userAccountDetail);
    }
}
